package cn.topcode.unicorn.wxsdk;

import cn.topcode.unicorn.utils.*;
import cn.topcode.unicorn.utils.http.HttpUtil;
import cn.topcode.unicorn.wxsdk.account.AccountInvoker;
import cn.topcode.unicorn.wxsdk.account.AccountInvokerImpl;
import cn.topcode.unicorn.wxsdk.base.*;
import cn.topcode.unicorn.wxsdk.customerservice.CustomerServiceInvoker;
import cn.topcode.unicorn.wxsdk.customerservice.CustomerServiceInvokerImpl;
import cn.topcode.unicorn.wxsdk.material.MaterialInvoker;
import cn.topcode.unicorn.wxsdk.material.MaterialInvokerImpl;
import cn.topcode.unicorn.wxsdk.menu.MenuInvoker;
import cn.topcode.unicorn.wxsdk.menu.MenuInvokerImpl;
import cn.topcode.unicorn.wxsdk.message.MessageInvoker;
import cn.topcode.unicorn.wxsdk.message.MessageInvokerImpl;
import cn.topcode.unicorn.wxsdk.message.receive.msg.Message;
import cn.topcode.unicorn.wxsdk.user.UserInvoker;
import cn.topcode.unicorn.wxsdk.user.UserInvokerImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * WX SDK调用入口类
 * @author Unicorn Lien
 * 2017年3月28日 下午10:19:33 创建
 */
public class WXContext {

    private static WechatMpDataSource wechatMpDataSource;

    /*用户管理操作接口*/
    private static UserInvoker userInvoker = null;
    
    /*消息相关接口*/
    private static MessageInvoker messageInvoker = null;
    
    /*菜单管理接口*/
    private static MenuInvoker menuInvoker = null;

    private static AccountInvoker accountInvoker = null;

    /*客服账号管理接口*/
    private static CustomerServiceInvoker customerServiceInvoker = null;

    /*素材管理接口*/
    private static MaterialInvoker materialInvoker = null;

    private static Object obj = new Object();
    
    private WXContext() {}
    
    /**
     * accessToken、jsTicket获取以及Invoker实例化加入双重同步锁保证线程安全
     */
    
    /**
     * accessToken获取接口
     * @author Unicorn Lien
     * 2017年3月28日 下午10:21:14 创建
     * @return  accessToken
     */
    public static String getToken(String mpId) {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(mpId);
        if(!isTokenAvailable(wechatMpInfo)) {
            return requestToken(wechatMpInfo);
        }
        return wechatMpInfo.getToken();
    }

    public static String getToken() {
        return getToken(getDefaultMpId());
    }

    public static String getAppId() {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(getDefaultMpId());
        return wechatMpInfo.getAppId();
    }

    /**
     * jsTicket获取接口
     * @author Unicorn Lien
     * 2017年3月28日 下午10:43:37 创建
     * @return  jsTicket
     */
    public static String getJsTicket(String mpId) {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(mpId);
        if(!isJsTicketAvailable(wechatMpInfo)) {
            return requestJsTicket(wechatMpInfo);
        }
        return wechatMpInfo.getJsTicket();
    }

    public static String getJsTicket() {
        return getJsTicket(getDefaultMpId());
    }

    /**
     * 获取用户信息
     * @param request 请求
     * @return
     */
    public static UserInfo getUserInfo(String mpId, HttpServletRequest request, HttpServletResponse response) {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(mpId);
        WebToken webToken = getWebToken(false, wechatMpInfo.getAppId(), wechatMpInfo.getSecret(), request, response);
        if(webToken != null) {
            if(webToken.getErrcode() == 0) {
                String url = String.format(ApiUrl.WebAuth.GET_USER_INFO,webToken.getAccessToken(),webToken.getOpenId());
                UserInfo userInfo = HttpUtil.getJson(url,UserInfo.class);
                if(!getWechatMpDataSource().existWechatUser(mpId, webToken.getOpenId())) {
                    getWechatMpDataSource().saveWechatUser(mpId, userInfo);
                }
                return userInfo;
            }else {
                throw new IllegalStateException("获取WebToken失败,errCode:" + webToken.getErrcode() + ",errMsg:" + webToken.getErrmsg());
            }
        }
        throw new IllegalStateException("获取WebToken为null");
    }

    public static UserInfo getUserInfo(HttpServletRequest request, HttpServletResponse response) {
        return getUserInfo(getDefaultMpId(), request, response);
    }

    /**
     * 获取用户信息 用于微信第三方登陆接口
     * @param accessToken
     * @param openId
     * @return
     */
    public static UserInfo getUserInfo(String accessToken, String openId) {
        if (StringUtil.isNotBlank(accessToken)
                && StringUtil.isNotBlank(openId)) {
            String url = String.format(ApiUrl.WebAuth.GET_USER_INFO, accessToken, openId);
            return HttpUtil.getJson(url, UserInfo.class);
        }
        throw new IllegalArgumentException("AccessToken或OpenId不能为null");
    }

    public static void registListener(Class<? extends Message> clazz, WXMessageListener<? extends Message> listener) {
        WXMessageCenter.getInstance().registListener(clazz, listener);
    }

    public static void unregistListener(Class<? extends Message> clazz, WXMessageListener<? extends Message> listener) {
        WXMessageCenter.getInstance().unregistListener(clazz, listener);
    }

    public static void unregistListener(Class<? extends Message> clazz) {
        WXMessageCenter.getInstance().unregistListener(clazz);
    }

    public static void registHandler(Class<? extends Message> clazz, WXMessageHandler<? extends Message> handler) {
        WXMessageCenter.getInstance().registHandler(clazz, handler);
    }
    
    public static void unregistHandler(Class<? extends Message> clazz) {
        WXMessageCenter.getInstance().unregistHandler(clazz);
    }
    
    /**
     * JS-SDK配置获取接口
     * @author Unicorn Lien
     * 2017年3月28日 下午10:43:58 创建
     * @param request   使用JS-SDK的页面地址，请使用request.getRequestURL().toString()
     * @return  JS-SDK 配置
     */
    public static JSSdkConfig getJSSDKConfig(String mpId, HttpServletRequest request) {
        String queryString = "";
        if(request.getQueryString() != null) {
            queryString = "?" + request.getQueryString();
        }
        String url = request.getRequestURL().toString() + queryString;

        return getJSSDKConfig(mpId, url);
    }

    public static JSSdkConfig getJSSDKConfig(HttpServletRequest request) {
        return getJSSDKConfig(getDefaultMpId(), request);
    }

    /**
     * JS-SDK配置获取接口
     * @param url   通过url
     * @return  JS-SDK 配置
     */
    public static JSSdkConfig getJSSDKConfig(String mpId, String url) {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(mpId);
        String jsTicket = getJsTicket(mpId);
        JSSdkConfig config = new JSSdkConfig();
        config.setAppId(wechatMpInfo.getAppId());
        String nonceStr = UuidUtil.getUUID().substring(0, 16);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        config.setNonceStr(nonceStr);
        config.setTimestamp(timestamp);
        String str = "jsapi_ticket="+jsTicket+"&noncestr="+nonceStr+"&timestamp="+timestamp+"&url="+url;
        String signature = DigestUtil.sha1b40(str);
        config.setSignature(signature);
        return config;
    }

    public static JSSdkConfig getJSSDKConfig(String url) {
        return getJSSDKConfig(getDefaultMpId(), url);
    }

    /**
     * 获取用户管理操作对象
     * @author Unicorn Lien
     * 2017年3月28日 下午10:45:16 创建
     * @return 用户管理操作对象
     */
    public static UserInvoker getUserInvoker() {
        if(userInvoker == null) {
            synchronized (UserInvoker.class) {
                if(userInvoker == null) {
                    userInvoker = new UserInvokerImpl();
                }
            }
        }
        return userInvoker;
    }

    public static MaterialInvoker getMaterialInvoker() {
        if(materialInvoker == null) {
            synchronized (MaterialInvoker.class) {
                if(materialInvoker == null) {
                    materialInvoker = new MaterialInvokerImpl();
                }
            }
        }
        return materialInvoker;
    }
    
    /**
     * 获取消息操作对象
     * @author Unicorn Lien
     * 2017年3月28日 下午10:45:47 创建
     * @return 消息操作对象
     */
    public static MessageInvoker getMessageInvoker() {
        if(messageInvoker == null) {
            synchronized (MessageInvoker.class) {
                if(messageInvoker == null) {
                    messageInvoker = new MessageInvokerImpl();
                }
            }
        }
        return messageInvoker;
    }
    
    /**
     * 获取菜单管理操作对象
     * @author Leo Lien
     * 2017年3月28日 下午10:46:12 创建
     * @return  菜单管理操作对象
     */
    public static MenuInvoker getMenuInvoker() {
        if(menuInvoker == null) {
            synchronized (MenuInvoker.class) {
                if(menuInvoker == null) {
                    menuInvoker = new MenuInvokerImpl();
                }
            }
        }
        return menuInvoker;
    }

    public static AccountInvoker getAccountInvoker() {
        if(accountInvoker == null) {
            synchronized (AccountInvoker.class) {
                if(accountInvoker == null) {
                    accountInvoker = new AccountInvokerImpl();
                }
            }
        }
        return accountInvoker;
    }

    public static CustomerServiceInvoker getCustomerServiceInvoker() {
        if(customerServiceInvoker == null) {
            synchronized (CustomerServiceInvoker.class) {
                if(customerServiceInvoker == null) {
                    customerServiceInvoker = new CustomerServiceInvokerImpl();
                }
            }
        }
        return customerServiceInvoker;
    }
    
    /**
     * 向微信服务器请求accessToken
     * @author Unicorn Lien
     * 2017年3月28日 下午10:46:33 创建
     */
    private static String requestToken(WechatMpInfo wechatMpInfo) {
        String url = String.format(ApiUrl.REQUEST_TOKEN, wechatMpInfo.getAppId(), wechatMpInfo.getSecret());
        Token tokenObj = HttpUtil.getJson( url, Token.class);
        if(tokenObj != null && StringUtil.isNotBlank(tokenObj.getAccessToken())) {
            wechatMpInfo.setToken(tokenObj.getAccessToken());
            wechatMpInfo.setTokenExpires(tokenObj.getExpiresIn());
            wechatMpInfo.setLastGetTokenTimemillis(System.currentTimeMillis());
            getWechatMpDataSource().saveWechatMpInfo(wechatMpInfo);
            return wechatMpInfo.getToken();
        }
        throw new IllegalStateException("获取公众号Token失败！");
    }

    /**
     * 判断accessToken是否过期
     * 预留10秒用于平滑切换token
     * @author Unicorn Lien
     * 2017年3月28日 下午10:46:59 创建
     * @return
     */
    private static boolean isTokenAvailable(WechatMpInfo wechatMpInfo) {
        if(StringUtil.isBlank(wechatMpInfo.getToken())) {
            return false;
        }
        return System.currentTimeMillis() <
                wechatMpInfo.getLastGetTokenTimemillis() +
                        wechatMpInfo.getTokenExpires() * 1000 + 10 * 1000;
    }
    
    /**
     * 向微信服务器请求jsTicket
     * @author Leo Lien
     * 2017年3月28日 下午10:47:45 创建
     */
    private static String requestJsTicket(WechatMpInfo wechatMpInfo) {
        String url = String.format(ApiUrl.REQUEST_JS_TICKET, getToken(wechatMpInfo.getMpId()));
        JsTicket ticket = HttpUtil.getJson(url, JsTicket.class);
        if(ticket != null && StringUtil.isNotBlank(ticket.getTicket())) {
            wechatMpInfo.setJsTicket(ticket.getTicket());
            wechatMpInfo.setJsTicketExpires(ticket.getExpiresIn());
            wechatMpInfo.setLastGetJsTicketTimemillis(System.currentTimeMillis());
            getWechatMpDataSource().saveWechatMpInfo(wechatMpInfo);
            return ticket.getTicket();
        }
        throw new IllegalStateException("获取公众号网页JsTicket失败！");
    }
    
    /**
     * 判断jsTicket是否过期
     * 预留10秒用于平滑切换jsTicket
     * @author Unicorn Lien
     * 2017年3月28日 下午10:47:25 创建
     */
    private static boolean isJsTicketAvailable(WechatMpInfo wechatMpInfo) {
        if(StringUtil.isBlank(wechatMpInfo.getJsTicket())) {
            return false;
        }
        return System.currentTimeMillis() <
                wechatMpInfo.getLastGetJsTicketTimemillis() +
                        wechatMpInfo.getJsTicketExpires() * 1000 + 10 * 1000;
    }

    /**
     * 获取默认公众号ID
     * @return
     */
    public static String getDefaultMpId() {
        return getWechatMpDataSource().getDefaultMpId();
    }

    /****************** getter and setter **********************/

    public static void setWechatMpDataSource(WechatMpDataSource wechatMpDataSource) {
        if(wechatMpDataSource == null) {
            throw new IllegalArgumentException("公众号信息源wechatMpDataSource不能为空");
        }
        WXContext.wechatMpDataSource = wechatMpDataSource;
        synchronized (obj) {
            obj.notifyAll();
        }
    }

    public static WechatMpDataSource getWechatMpDataSource() {
        if(wechatMpDataSource == null) {
            synchronized (obj) {
                try {
                    obj.wait(1000 * 30);
                } catch (InterruptedException e) {
                }
            }
        }
        return wechatMpDataSource;
    }

    /**
     * 网页授权获取用户信息
     * @param request 请求
     * @return
     */
    private static WebToken getWebToken(boolean base, String appId, String secret, HttpServletRequest request, HttpServletResponse response) {
        WebToken webToken = (WebToken) request.getSession().getAttribute("webToken" + base);
        Long getWebTokenLastTime = (Long) request.getSession().getAttribute("getWebTokenLastTime");
        if(webToken != null && getWebTokenLastTime != null) {
            if(getWebTokenLastTime + webToken.getExpiresIn() * 1000L + 10 * 1000 > System.currentTimeMillis()) {
                return webToken;
            }
        }
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        if(StringUtil.isBlank(code)) {
            return redict(base, appId, state, request, response);
        }
        String url = String.format(ApiUrl.WebAuth.GET_WEB_TOKEN, appId,secret,code);
        webToken = HttpUtil.getJson(url, WebToken.class);
        if(webToken != null && StringUtil.isNotBlank(webToken.getOpenId())) {
            request.getSession().setAttribute("webToken" + base, webToken);
            request.getSession().setAttribute("getWebTokenLastTime", System.currentTimeMillis());
            return webToken;
        }
        if(!"DontRepeat".equals(state)) {
            return redict(base, appId, "DontRepeat", request, response);
        }
        throw new IllegalStateException("获取微信Token失败！");
    }

    private static WebToken redict(boolean base, String appId, String state, HttpServletRequest request, HttpServletResponse response) {
        String url = request.getRequestURL().toString();
        url += request.getQueryString() != null ? "?" + request.getQueryString().replaceAll("code=","old_code=") : "";
        if(base) {
            url = String.format(ApiUrl.WebAuth.OAUTH2_SNSAPI_BASE, appId, url, state);
        }else {
            url = String.format(ApiUrl.WebAuth.OAUTH2_SNSAPI_USERINFO, appId, url, state);
        }
        try {
            response.sendRedirect(url);
        } catch (IOException e) {
        }
        throw new IgnoreException("重定向到微信服务器进行OAuth2.0授权");
    }

    public static WebToken getWebToken(String mpId, HttpServletRequest request, HttpServletResponse response) {
        WechatMpInfo wechatMpInfo = getWechatMpDataSource().findByMpId(mpId);
        return getWebToken(true, wechatMpInfo.getAppId(), wechatMpInfo.getSecret(), request, response);
    }

    public static WebToken getWebToken(HttpServletRequest request, HttpServletResponse response) {
        return getWebToken(getDefaultMpId(), request, response);
    }
}
